﻿using System;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for setting the accidental of a note.
    /// </summary>
    public class SetAccidentalCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SetAccidentalCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The undo command stack</param>
        /// <param name="redoStack">The redo command stack</param>
        public SetAccidentalCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<Elements.CWNotation.AccidentalType>("Accidental", ParameterDirection.In);
            AddParameter<Elements.CWNotation.Note>("Note", ParameterDirection.In);

            Name = "Change Accidental";
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Processes an affected section.
        /// </summary>
        /// <param name="section">
        /// <para>
        /// Type: <see cref="Model.Elements.MusicSectionElement"/>
        /// </para>
        /// <para>
        /// The section.
        /// </para>
        /// </param>
        private void ProcessAffectedSection(Model.Elements.MusicSectionElement section)
        {
            var modelNote = (Model.Elements.CWNotation.Note)Note.AdaptedElement;
            
            if (!section.Parts.Contains(modelNote.FindOwner<Model.Elements.CWNotation.Staff>().Descriptor.FindOwner<Model.Elements.PartDescriptorElement>()))
                return;

            modelNote.FindOwner<Model.Elements.CWNotation.Chord>().RaiseEnvironmentChanged();

            if (modelNote.FindOwner<Model.Elements.PageElement>() != null)
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(modelNote.FindOwner<Model.Elements.CWNotation.System>());
            else
                Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(modelNote.FindOwner<Model.Elements.CWNotation.System>());
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var modelNote = (Model.Elements.CWNotation.Note)Note.AdaptedElement;
            var accidentalValue = Model.Elements.CWNotation.Accidental.AccidentalTypeToValue(
                (Model.Elements.CWNotation.AccidentalType)Enum.Parse(typeof(Model.Elements.CWNotation.AccidentalType), Accidental.ToString())
                );
            var tone = modelNote.PresentedTone;
            oldAccidental = tone.Accidental;
            tone.Accidental = accidentalValue;

            var measure = tone.FindOwner<Model.Elements.MeasureElement>();
            var references = tone.FindOwner<Model.Elements.CompositionElement>().GetReferencingPages(measure, measure);
            foreach (var reference in references)
                ProcessAffectedSection(reference.Section);
            
            var composition = measure.PackagePart as Model.Packaging.CompositionPart;
            ProcessAffectedSection(composition.LinearView.MusicSection);

            Note.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            var modelNote = (Model.Elements.CWNotation.Note)Note.AdaptedElement;
            var accidentalValue = Model.Elements.CWNotation.Accidental.AccidentalTypeToValue(
                (Model.Elements.CWNotation.AccidentalType)Enum.Parse(typeof(Model.Elements.CWNotation.AccidentalType), Accidental.ToString())
                );
            var tone = modelNote.PresentedTone;
            tone.Accidental = accidentalValue;

            var measure = tone.FindOwner<Model.Elements.MeasureElement>();
            var references = tone.FindOwner<Model.Elements.CompositionElement>().GetReferencingPages(measure, measure);
            foreach (var reference in references)
                ProcessAffectedSection(reference.Section);

            var composition = measure.PackagePart as Model.Packaging.CompositionPart;
            ProcessAffectedSection(composition.LinearView.MusicSection);

            Note.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            var modelNote = (Model.Elements.CWNotation.Note)Note.AdaptedElement;
            var tone = modelNote.PresentedTone;
            tone.Accidental = oldAccidental;

            var measure = tone.FindOwner<Model.Elements.MeasureElement>();
            var references = tone.FindOwner<Model.Elements.CompositionElement>().GetReferencingPages(measure, measure);
            foreach (var reference in references)
                ProcessAffectedSection(reference.Section);

            var composition = measure.PackagePart as Model.Packaging.CompositionPart;
            ProcessAffectedSection(composition.LinearView.MusicSection);

            Note.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the accidental.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.AccidentalType"/>
        /// </para>
        /// <para>
        /// The accidental.<br/>
        /// This property wraps the "Accidental" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.AccidentalType Accidental
        {
            get { return GetParameterValue<Elements.CWNotation.AccidentalType>("Accidental"); }
            set { SetParameterValue<Elements.CWNotation.AccidentalType>("Accidental", value); }
        }

        /// <summary>
        /// Gets or sets the note whose accidental is being set.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.CWNotation.Note"/>
        /// </para>
        /// <para>
        /// The note whose accidental is being set.<br/>
        /// This property wraps the "Note" command parameter.
        /// </para>
        /// </value>
        public Elements.CWNotation.Note Note
        {
            get { return GetParameterValue<Elements.CWNotation.Note>("Note"); }
            set { SetParameterValue<Elements.CWNotation.Note>("Note", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the original accidental value.
        /// </summary>
        private double oldAccidental;

        #endregion
    }
}
